﻿module Physics.Chapter2.Main

open Casanova
open Casanova.Input
open Casanova.Core
open Casanova.Coroutines
open Casanova.Utilities
open Casanova.Math
open Casanova.Game
open Casanova.Drawing
open Casanova.StandardLibrary
open Casanova.StandardLibrary.Core
open Physics.Chapter2.RigidBody

type [<CasanovaWorld>] World = {
  Models              : ModelLayer
  Camera              : Camera3D
  Cubes               : RigidBody[]
  SelectedIndex       : Var<int>
  AvgDt               : Rule<float32<s>>
  DebugText           : Text
} with 
  member world.SelectedCube = world.Cubes.[!world.SelectedIndex]

  static member ModelsView'(self:World, dt:float32<s>) = 
    self.Camera.View
  static member ModelsProjection'(self:World, dt:float32<s>) = 
    self.Camera.Projection

  member world.Bodies = world.Cubes

  static member AvgDt'(world:World, dt : float32<s>) =
    lerp 0.9f (!world.AvgDt) dt
  
  static member DebugTextString'(world:World, dt : float32<s>) = 
    sprintf "Selected model: %d\nFPS: %3.1f\n" 
            !world.SelectedIndex
            (1.0f<s> / !world.AvgDt)


let rec start_game (args : StartGameArgs) =
  let models = ModelLayer.Create(args.GraphicsDevice, args.Content)

  let camera = Camera3D.Create(Vector3.UnitZ * 5.0f + Vector3.UnitY * 2.5f, 1.5f, args.GraphicsDevice.Viewport.AspectRatio, 0.1f, 10000.0f)

  let friction = 0.01f

  let cube1 = RigidBody.CreateCube(models, Vector3.Create(2.0f, 0.0f, 0.0f), 
                                    Vector3.One, 1.0f / 50.0f<kg>, friction, "cube")
  let cube2 = RigidBody.CreateCube(models, Vector3.Create(0.0f, 0.0f, 2.0f), 
                                    Vector3.One, 1.0f / 50.0f<kg>, friction, "cube")
  let cube3 = RigidBody.CreateCube(models, Vector3.Create(2.0f, 0.0f, 2.0f),  
                                    Vector3.One, 1.0f / 50.0f<kg>, friction, "cube")

  let world =
    {
      Models              = models
      Camera              = camera
      Cubes               = [|cube1; cube2; cube3|]
      SelectedIndex       = Var.Create(0)
      AvgDt               = Rule.Create 0.0f<s>
      DebugText           = Text.Create(args.DefaultLayer, -Vector2.UnitY * 400.0f, Vector2.One * 300.0f) }

  let inline (!) x = immediate_lookup x

  let main = yield_

  do hide_mouse_cursor()

  let applied_force_multiplier b  = 20.0f<_> / b.MassInv
  let applied_torque_multiplier b = 10.0f<_> / b.MassInv

  let input =
    [
      wait_key_press Keys.Tab     => co{ 
        return world.SelectedIndex := (!world.SelectedIndex + 1) % (world.Bodies.Length - 1) }

      wait_key_press Keys.F9      => args.Save("RotatingCube")
      wait_key_press Keys.F10     => args.Load("RotatingCube")

      wait_key_down  Keys.Up      => co{ return world.SelectedCube.AddF(-Vector3<_>.UnitZ * applied_force_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.Down    => co{ return world.SelectedCube.AddF( Vector3<_>.UnitZ * applied_force_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.Left    => co{ return world.SelectedCube.AddF(-Vector3<_>.UnitX * applied_force_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.Right   => co{ return world.SelectedCube.AddF( Vector3<_>.UnitX * applied_force_multiplier(world.SelectedCube)) }

      wait_key_down  Keys.I       => co{ return world.SelectedCube.AddT( Vector3<_>.UnitZ * applied_torque_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.K       => co{ return world.SelectedCube.AddT(-Vector3<_>.UnitZ * applied_torque_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.J       => co{ return world.SelectedCube.AddT(-Vector3<_>.UnitY * applied_torque_multiplier(world.SelectedCube)) }
      wait_key_down  Keys.L       => co{ return world.SelectedCube.AddT( Vector3<_>.UnitY * applied_torque_multiplier(world.SelectedCube)) }

      wait_key_down  Keys.W       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Forward * 30.0f<m/s> }
      wait_key_down  Keys.A       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Left * 30.0f<m/s> }
      wait_key_down  Keys.S       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Backward * 30.0f<m/s> }
      wait_key_down  Keys.D       => co{ return world.Camera.Velocity := !world.Camera.Velocity + world.Camera.Right * 30.0f<m/s> }

      wait_key_down  Keys.Space   => 
        co{ 
          world.SelectedCube.ApplyExternalForce(
            world.SelectedCube.Size * 0.5f * Vector3(0.0f, 1.0f, 1.0f), 
            world.SelectedCube.FromLocalSpaceNormal(Vector3<N>.UnitY * (applied_force_multiplier world.SelectedCube) * 0.1f))
        }

      wait_mouse_move =>> 
        fun (mx,my) ->
          co{
            do world.Camera.Rotation := Vector2.Create(mx,my) * 0.1f<rad/s>
            do set_mouse_position(200,200)
          }

      wait_key_down Keys.Escape  => args.Quit

      wait_key_down Keys.X  =>
        co{
          do time_speed <- 0.01f
        }

      wait_key_down Keys.Z  =>
        co{
          do time_speed <- 1.0f
        }

    ]
  world, main, input
